#!/usr/bin/python

# SYSTEM SETTINGS SECTION

DICTIONARY_DIR = 'dictionary'
ADDITIONS_DIR = 'additions'
CONTACTS_DIR = 'contacts'
LOGS_DIR = 'logs'

EMAIL_FILE = './contacts/subscribe.txt'

CONFIG_FILE = 'config2.txt'
COMMAND_PREFIX = '/'
ENCODING = 'utf-8'
DELIMITER = '\t'

USER_HELP_MSG = """
/help: show this help message
/invite: invite a friend to use this dictionary
/add: add new words to the dictionary so that it can be used by others
/stats: show system statistics
/about: information about en2mm system
"""

ADMIN_HELP_MSG = """
/status: set the status message of the
/broadcast: send a chat message to all subscribers
/exit: shutdown the bot completely
"""

WELCOME_MSG = """Thank you for adding EN2MM system.
To find the definition of an English word, simply enter the word in the chat box.
Type /help to find out what other things you can do."""

ABOUT_MSG = """EN2MM (0.2) is a GTalk Based English-Myanmar dictionary lookup system.

MZ Dictionary by Mystery Zillion Group (www.mysteryzillion.org)

Gtalk Dictionary Lookup Bot by Myanmar Language Technology (www.mmlt.org)

Hosting and System Administration by Myanmar Geeks (www.mmgeeks.org)"""

LOOKUP_NOT_FOUND_MSG = """Sorry, your word is not in the dictionary yet.
Please add the definition of this word by using /add command.
"""

LOOKUP_IN_ADDS_MSG = """User suggested definition of %s:
%s"""

ADD_FORMAT_MSG = """The format is
/add [Word] [Word Type] [Description]

For example:
/add horse noun %s""" % (u'\u103b\u1019\u1004\u1039\u1038')

ADD_SUCCESS_MSG = """Thank you. The word "%s" has been added to the system."""

INVITE_FORMAT_MSG = """The format is
/invite [emailaddress]

For example:
/invite %s"""

# END OF SYSTEM SETTINGS

import xmpp
import wordnet

import os
import re
import sys
import time
import codecs


def lookup_WordNet(lookup):
	try:	# If the lookup word is not in ASCII range, it will raise exception
		lookup = str(lookup)
	except StandardError:
		return None
	message = ''
	try:
		if wordnet.V.has_key(lookup):	# If the word is a verb
			message = "(v) " + str(wordnet.V[lookup].getSenses()[0].synset.gloss)
	except StandardError:
		pass
	try:
		if wordnet.N.has_key(lookup):	# If the word is a noun
			message += "\n" + "(n) " + str(wordnet.N[lookup].getSenses()[0].synset.gloss)
	except StandardError:
		pass
	try:
		if wordnet.ADJ.has_key(lookup):	# If the word is an adjective
			message += "\n" + "(adj) " + str(wordnet.ADJ[lookup].getSenses()[0].synset.gloss)
	except StandardError:
		pass
	try:
		if wordnet.ADV.has_key(lookup):	# If the word is an adverb
			message += "\n" + "(adv) " + str(wordnet.ADV[lookup].getSenses()[0].synset.gloss)
	except StandardError:
		pass
	return message
	
	
def process_Lookup(client, sender, lookup):
	lookup = lookup.lower()
	if en2mm.has_key(lookup):
		reply = en2mm[lookup]
	else:
		wordnetResult = lookup_WordNet(lookup)
		if addedDict.has_key(lookup):
			reply = LOOKUP_IN_ADDS_MSG % (lookup, addedDict[lookup])
		elif wordnetResult:
			reply = wordnetResult
		else:
			reply = LOOKUP_NOT_FOUND_MSG
	client.send(xmpp.Message(sender, reply))


def process_Command(client, sender, command):
	global addedDict
	senderJID = '%s@%s' % (sender.getNode(), sender.getDomain())
	command = command.split()
	cmd = command[0]

# USER COMMANDS SECTION

	# Display user help
	if cmd == 'help':
		reply = USER_HELP_MSG

	# Add a dictionary entry
	elif cmd == 'add':
		if len(command) < 4:
			reply = ADD_FORMAT_MSG
		else:
			word = command[1]
			category = command[2]
			description = ' '.join(command[3:])
			if addedDict.has_key(command[1]):
				addedDict[word] += '(%s) %s\n' % (category, description)
			else:
				addedDict[word] = '(%s) %s\n' % (category, description)

			addedFile = codecs.open(addedFilePath, 'a', encoding=ENCODING)
			addedFile.write('%s\t%s\t%s\t%s\n' % (senderJID, word, category, description))
			addedFile.close()	
			reply = ADD_SUCCESS_MSG % word


	# Let the bot subscribe to another email address
	elif cmd == 'invite':
		if len(command) != 2:
			reply = INVITE_FORMAT_MSG % senderJID
		else:
			guest = command[1]
			if re.match('\S+@\S+\.\w+', guest):	# Check for correct email address format
				if guest in roster.getItems():
					reply = '%s is already in the user list.' % guest
				else:
					reply = 'Thank you. %s has been invited to use this system.' % guest
					guestNode, guestDomain = guest.split('@')
					roster.Subscribe(xmpp.protocol.JID(node=guestNode, domain=guestDomain))
			else:
				reply = '%s is not a valid email address.' % guest

	elif cmd == 'about':
		reply = ABOUT_MSG

	# Display system statistics
	elif cmd == 'stats':
		reply ="""
Total words: %d
Total users: %d
Total additions: %d
""" % (totalWords, len(roster.getItems()), totalAdded)

	# If the sender is not in the admin list, don't check any further for admin commands
	elif senderJID not in admins:
		reply = 'invalid command'

# ADMIN COMMANDS SECTION

	# Display administrator help
	elif cmd == 'admin':	
		reply = ADMIN_HELP_MSG

	# Set the status message
	elif cmd == 'status':
		if len(command) < 2:
			reply = 'invalid number of parameters'
		else:
			presence = xmpp.protocol.Presence()
			presence.setStatus(' '.join(command[1:]))
			client.send(presence)
			reply = 'status set'
	
	# Broadcast a chat message to all subscribers
	elif cmd == 'broadcast':
		message = ' '.join(command[1:])
		for contact in roster.getItems():
			client.send(xmpp.Message(contact, message))
		return

	# Shutdown the bot completely
	elif cmd == 'exit':
		reply = 'exiting'
		sys.exit(0)

	else:
		reply = 'invalid admin command'
	
	client.send(xmpp.Message(sender, reply))


def handle_Message(client, message):
	"Processes commands and dictionary lookup requests"
	sender = message.getFrom()
	request = message.getBody()
	if request:
		request = request.strip()
		if request.startswith(COMMAND_PREFIX):
			process_Command(client, sender, request[1:])
		else:
			process_Lookup(client, sender, request)


def handle_Presence(client, presence):
	"Automatically accepts user subscriptions"
	global roster
	sender = presence.getFrom()
	type = presence.getType()
	if type == 'subscribe':
		roster.Authorize(sender)
		client.send(xmpp.Message(sender, WELCOME_MSG))
		print 'MESSAGE: subscribed by ', sender


def read_ConfigFile():
	global username, password, domain, url, port, admins
	configs = {}
	for line in open(CONFIG_FILE).readlines():
		line = line.strip()
		if len(line) > 0 and not line.startswith('#'):	# If not a blank line or a comment,
			key, value = line.split(':')
			key = key.strip().lower()
			value = value.strip()
			if configs.has_key(key):
				configs[key].append(value);
			else:
				configs[key] = [value]

	username = configs['username'][0]
	password = configs['password'][0]
	domain = configs['domain'][0]
	url = configs['url'][0]
	port = int(configs['port'][0])
	admins = configs['admin']


def load_Dictionary():
	if not os.access(DICTIONARY_DIR, os.F_OK):
		print 'ERROR: cannot locate dictionary directory.'
		sys.exit(1)

	global en2mm, totalWords
	en2mm = {}
	totalWords = 0
	for fileName in os.listdir(DICTIONARY_DIR):
		if fileName.endswith('.txt'):
			filePath = os.path.join(DICTIONARY_DIR, fileName)
			lineNum = 1
			print 'MESSAGE: reading', filePath
			for line in codecs.open(filePath, encoding=ENCODING).readlines():
				line = line.strip()
				columns = line.split(DELIMITER)
				if len(columns) >= 3:
					word = columns[0].strip()
					category = columns[1].strip()
					description = columns[2].strip()
					if en2mm.has_key(word):
						en2mm[word] += '(%s) %s\n' % (category, description)
					else:
						en2mm[word] = '(%s) %s\n' % (category, description)
						totalWords += 1
				else:
					print 'WARNING: invalid entry at line', lineNum
				lineNum += 1
	print 'MESSAGE: number of words in dictionary -', totalWords


def load_Additions():
	global addedDict, addedFilePath, totalAdded
	addedDict = {}
	totalAdded = 0
	if os.access(ADDITIONS_DIR, os.F_OK):
		for fileName in	os.listdir(ADDITIONS_DIR):
			filePath = os.path.join(ADDITIONS_DIR, fileName)
			lineNum = 1
			print 'MESSAGE: reading', filePath
			for line in codecs.open(filePath, encoding=ENCODING).readlines():
				line = line.strip()
				columns = line.split(DELIMITER)
				if len(columns) >= 3:
					word = columns[1].strip()
					category = columns[2].strip()
					description = columns[3].strip()
					if addedDict.has_key(word):
						addedDict[word] += '(%s) %s\n' % (category, description)
					else:
						addedDict[word] = '(%s) %s\n' % (category, description)
						totalAdded += 1
				else:
					print 'WARNING: invalid entry at line', lineNum
				lineNum += 1
	else:
		os.mkdir(ADDITIONS_DIR)
	print 'MESSAGE: number of words added previously -', totalAdded
	addedFilePath = os.path.join(ADDITIONS_DIR, 'added_%s.txt' % (time.strftime('%Y%m%d_%H%M%S')))


def setup_Connection():
	global client, roster
	print 'MESSAGE: connecting to server'
	client = xmpp.Client(domain, debug=[])
	client.connect(server = (url, port))
	client.auth(username, password)
	client.sendInitPresence()
	roster = client.getRoster()
	client.RegisterHandler('message', handle_Message)
	client.RegisterHandler('presence', handle_Presence)


def save_Contacts():
	"Downloads the contact list for backup purposes"
	if not os.access(CONTACTS_DIR, os.F_OK):
		os.mkdir(CONTACTS_DIR)
	print 'MESSAGE: downloading contact list'
	contacts = roster.getItems()
	filePath = os.path.join(CONTACTS_DIR, 'contacts_%s_%s.txt' % (time.strftime('%Y%m%d_%H%M%S'), len(contacts)))
	contactsFile = open(filePath, 'w')
	contactsFile.write('\n'.join(contacts))
	contactsFile.close()
	print 'MESSAGE: contact list saved as', filePath


def subscribe_Contacts(emailFile):
	"Subscribes to all emails in the email file"
	global roster
	if emailFile:
		count = 0
		print 'MESSAGE: Subscribing to email list.'
		for line in open(emailFile).readlines():
			line = line.strip()
			roster.Subscribe(line)
			print '.',
			time.sleep(0.1)
		print ''


if __name__ == '__main__':
	read_ConfigFile()
	load_Dictionary()
	load_Additions()
	setup_Connection()
#	subscribe_Contacts(EMAIL_FILE)
	save_Contacts()
	while True:
		client.Process(1)
